package com.hao.utils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;

/**
 * java源文件注释移除器，可用于删除java源文件中的行注释和块注释， <br>
 * 并处理了字符串中包含/*或//的情况，不会误删字符串。
 * 
 * @author Hao
 *
 */
public class JavaCommentRemover {
	/**
	 * 从源代码输入流读取代码，移除java源代码中的注释，将移除注释的代码写入输出流
	 * 
	 * @param is
	 *            源代码输入流
	 * @param os
	 *            目标输出流
	 * @param charset
	 *            编码
	 * @throws IOException
	 *             读取出错时抛出IOException
	 */
	public static void removeComment(InputStream is, OutputStream os,
			Charset charset) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(is,
				charset));
		BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os,
				charset));
		boolean inBlockComment = false;
		boolean inLineComment = false;
		boolean inDoubleQuotes = false;
		boolean inSingleQuotes = false;
		int currentChar = reader.read();
		int nextChar;
		while (currentChar != -1) {
			nextChar = reader.read();
			if (inBlockComment) {
				// 在块注释内
				if (currentChar == '*' && nextChar == '/') {
					inBlockComment = false;
					nextChar = reader.read();
				}
			} else if (inLineComment) {
				// 在行注释内
				if (currentChar == '\n') {
					inLineComment = false;
					writer.write(currentChar);
				}
			} else if (inDoubleQuotes) {
				// 在双引号中，即字符串内
				if (currentChar == '\\') {
					// 解决转义符
					writer.write(currentChar);
					writer.write(nextChar);
					nextChar = reader.read();
				} else {
					if (currentChar == '"') {
						// 双引号结束
						inDoubleQuotes = false;
					}
					writer.write(currentChar);
				}
			} else if (inSingleQuotes) {
				// 在单引号中，即字符
				if (currentChar == '\\') {
					// 解决转义符
					writer.write(currentChar);
					writer.write(nextChar);
					nextChar = reader.read();
				} else {
					if (currentChar == '\'') {
						// 单引号结束
						inSingleQuotes = false;
					}
					writer.write(currentChar);
				}
			} else {
				// 普通情况
				if (currentChar == '/' && nextChar == '*') {
					// 进入块注释
					inBlockComment = true;
					nextChar = reader.read();
				} else if (currentChar == '/' && nextChar == '/') {
					// 进入行注释
					inLineComment = true;
					nextChar = reader.read();
				} else if (currentChar == '"') {
					// 进入双引号
					inDoubleQuotes = true;
					writer.write(currentChar);
				} else if (currentChar == '\'') {
					// 进入单引号
					inSingleQuotes = true;
					writer.write(currentChar);
				} else {
					// 其它情况
					writer.write(currentChar);
				}
			}
			currentChar = nextChar;
		}
		writer.flush();
	}

	/**
	 * 移除指定java源文件中代码注释，并写入目标文件
	 * 
	 * @param src
	 *            java源文件
	 * @param dest
	 *            保存移除注释后代码的目标文件
	 * @param charset
	 *            文件编码
	 * @throws IOException
	 *             文件不存在或文件读写错误
	 */
	public static void removeComment(File src, File dest, Charset charset)
			throws IOException {
		if (src.equals(dest)) {
			// 如果源与目标相同
			removeComment(src, charset);
		} else {
			InputStream is = new FileInputStream(src);
			if (!dest.exists()) {
				File parent = dest.getParentFile();
				if (parent != null) {
					parent.mkdirs();
				}
				dest.createNewFile();
			}
			OutputStream os = new FileOutputStream(dest);
			try {
				removeComment(is, os, charset);
			} finally {
				closeQuietly(is);
				closeQuietly(os);
			}
		}
	}

	private static void closeQuietly(Closeable o) {
		if (o != null) {
			try {
				o.close();
			} catch (IOException e) {
			}
		}
	}

	/**
	 * 移除java源代码文件中的注释，并保存到相同的文件中
	 * 
	 * @param javaSourceFile
	 *            java源代码文件
	 * @param charset
	 *            文件编码
	 * @throws IOException
	 *             文件不存在或者读写错误
	 */
	public static void removeComment(File javaSourceFile, Charset charset)
			throws IOException {
		File tmp = File.createTempFile("NoComents", ".java");
		removeComment(javaSourceFile, tmp, charset);
		javaSourceFile.delete();
		tmp.renameTo(javaSourceFile);
	}

}
